//
//  TTHTTPRequest.m
//  Tint
//
//  Created by Kevin Wu on 2/23/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTHTTPRequest.h"
#import "TTNAIManager.h"



@interface TTHTTPRequest (Private)

- (void)beginUsingNetwork;
- (void)endUsingNetwork;

- (void)addHeadersForRequest:(NSMutableURLRequest *)request;
- (void)addBodyForRequest:(NSMutableURLRequest *)request;
- (void)addMethodForRequest:(NSMutableURLRequest *)request;

+ (void)requestThreadBody:(id)object;

@end


@implementation TTHTTPRequest (Private)

- (void)beginUsingNetwork
{
    if ( _shouldUpdateNetworkActivityIndicator ) {
        [[TTNAIManager sharedObject] addNetworkUser:self];
    }
}

- (void)endUsingNetwork
{
    if ( _shouldUpdateNetworkActivityIndicator ) {
        [[TTNAIManager sharedObject] removeNetworkUser:self];
    }
}



- (void)addHeadersForRequest:(NSMutableURLRequest *)request
{
    for ( NSString *header in [_requestHeaders keyEnumerator] ) {
        NSString *value = [_requestHeaders objectForKey:header];
        [request addValue:value forHTTPHeaderField:header];
    }
    
    [request addValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
    [request addValue:[self classString] forHTTPHeaderField:@"User-Agent"];
}

- (void)addBodyForRequest:(NSMutableURLRequest *)request
{
    NSUInteger postLength = [_requestBody length];
    if ( postLength > 0 ) {
        
        [request setHTTPBody:_requestBody];
        
        NSString *header = [[NSString alloc] initWithFormat:@"%u", postLength];
        [request addValue:header forHTTPHeaderField:@"Content-Length"];
        TTRelease(header);
        
        self.requestMethod = @"POST";
    }
}

- (void)addMethodForRequest:(NSMutableURLRequest *)request
{
    if ( ![_requestMethod isEqualToString:@"POST"] ) {
        self.requestMethod = @"GET";
    }
    [request setHTTPMethod:_requestMethod];
}


+ (void)requestThreadBody:(id)object
{
    while ( YES ) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        [[NSRunLoop currentRunLoop] run];
        [pool release];
    }
}

@end





@implementation TTHTTPRequest



#pragma mark - Accessors

@synthesize address = _address;
@synthesize destinationPath = _destinationPath;
@synthesize runLoopMode = _runLoopMode;
@synthesize shouldUpdateNetworkActivityIndicator = _shouldUpdateNetworkActivityIndicator;

@synthesize requestHeaders = _requestHeaders;
@synthesize requestBoundary = _requestBoundary;
@synthesize requestBody = _requestBody;
@synthesize requestMethod = _requestMethod;
@synthesize connection = _connection;
@synthesize timeoutInterval = _timeoutInterval;
@synthesize cachePolicy = _cachePolicy;

@synthesize response = _response;
@synthesize responseData = _responseData;
@synthesize responseFileHandle = _responseFileHandle;

@synthesize showAccurateProgress = _showAccurateProgress;
@synthesize progress = _progress;
@synthesize lastBytesRead = _lastBytesRead;
@synthesize totalBytesRead = _totalBytesRead;
@synthesize contentLength = _contentLength;



#pragma mark - Memory

- (id)initWithAddress:(NSString *)address
{
    self = [super init];
    if ( self ) {
        
        TTCopy(address, _address);
        _destinationPath = nil;
        self.runLoopMode = NSRunLoopCommonModes;
        _shouldUpdateNetworkActivityIndicator = YES;
        
        
        _requestHeaders = nil;
        _requestBoundary = nil;
        _requestBody = nil;
        _requestMethod = [[NSString alloc] initWithString:@"GET"];
        _connection = nil;
        _timeoutInterval = 15.0;
        _cachePolicy = NSURLRequestUseProtocolCachePolicy;
        
        
        _response = nil;
        _responseData = nil;
        _responseFileHandle = nil;
        
        
        _showAccurateProgress = NO;
        _progress = 0.0;
        _lastBytesRead = 0;
        _totalBytesRead = 0;
        _contentLength = 0;
        
        
        _step = TTOperationStepReady;
        [self willChangeValueForKey:@"isReady"];
        _ready = YES;
        [self didChangeValueForKey:@"isReady"];
        
    }
    return self;
}

- (void)dealloc
{
    TTRelease(_address);
    TTRelease(_destinationPath);
    TTRelease(_runLoopMode);
    _shouldUpdateNetworkActivityIndicator = YES;
    
    TTRelease(_requestHeaders);
    TTRelease(_requestBoundary);
    TTRelease(_requestBody);
    TTRelease(_requestMethod);
    TTRelease(_connection);
    _timeoutInterval = 15.0;
    _cachePolicy = NSURLRequestUseProtocolCachePolicy;
    
    TTRelease(_response);
    TTRelease(_responseData);
    [_responseFileHandle closeFile];
    TTRelease(_responseFileHandle);
    
    _showAccurateProgress = NO;
    _progress = 0.0;
    _lastBytesRead = 0;
    _totalBytesRead = 0;
    _contentLength = 0;
    
    [super dealloc];
}



#pragma mark - Public

- (void)startAsynchronous
{
    [[[self class] requestOperationQueue] addOperationSafely:self];
}

- (NSData *)startSynchronous
{
    if ( ![self isCancelled] && (_step == TTOperationStepFinished) ) return _responseData;
    if ( [self isCancelled] ) return nil;
    if ( [self isExecuting] ) return nil;
    if ( [self isReady] ) {
        [self beginUsingNetwork];
        
        _step = TTOperationStepExecuting;
        _ready = NO;
        _executing = YES;
        
        NSURL *URL = [[NSURL alloc] initWithString:_address];
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL 
                                                                    cachePolicy:_cachePolicy 
                                                                timeoutInterval:_timeoutInterval];
        TTRelease(URL);
        [self addHeadersForRequest:request];
        [self addBodyForRequest:request];
        [self addMethodForRequest:request];
        
        NSError *error = nil;
        NSURLResponse *response = nil;
        NSData *responseData = [NSURLConnection sendSynchronousRequest:request 
                                                     returningResponse:&response 
                                                                 error:&error];
        TTRelease(request);
        
        TTRetain(response, _response);
        TTRetain(responseData, _responseData);
        
        _step = TTOperationStepFinished;
        _executing = NO;
        _finished = YES;
        
        [self endUsingNetwork];
        
        return _responseData;
    }
    return nil;
}

- (NSString *)responseString
{
    if ( _response && _responseData ) {
        NSStringEncoding textEncoding = NSUTF8StringEncoding;
        if ( _response.textEncodingName ) {
            CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)(_response.textEncodingName));
            textEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
        }
        
        return [[[NSString alloc] initWithData:_responseData encoding:textEncoding] autorelease]; // AUTO
    }
    return nil;
}

- (NSDictionary *)responseHeaders
{
    return [(NSHTTPURLResponse *)_response allHeaderFields]; // AUTO
}

- (void)addRequestHeader:(NSString *)header value:(NSString *)value
{
    if ( [header length] > 0 ) {
        
        if ( _requestHeaders == nil ) {
            _requestHeaders = [[NSMutableDictionary alloc] init];
        }
        
        if ( value ) {
            // Add header
            [_requestHeaders setValue:value forKey:header];
        } else {
            // Remove header
            [_requestHeaders removeObjectForKey:header];
        }
    }
}

- (void)setRequestHeaders:(NSDictionary *)headers
{
    TTRelease(_requestHeaders);
    if ( [headers count] > 0 ) {
        _requestHeaders = [[NSMutableDictionary alloc] initWithDictionary:headers];
    }
}

- (void)setRequestBody:(NSData *)body
{
    TTRelease(_requestBody);
    if ( [body length] > 0 ) {
        _requestBody = [[NSData alloc] initWithData:body];
    }
}



- (void)constructMultipartDataWithParameters:(NSDictionary *)parameters
{
    if ( [parameters count] < 1) {
        return;
    }
    
    
    // TODO: update implementation
    
    if ( [_requestBoundary length] < 1) {
        TTRelease(_requestBoundary);
        _requestBoundary = [NSString newUUIDString];
    }
    
    
    // Set boundary header
    NSString *headerValue = [[NSString alloc] initWithFormat:@"multipart/form-data; boundary=%@", _requestBoundary];
    [self addRequestHeader:@"Content-Type" value:headerValue];
    TTRelease(headerValue);
    
    
     TTRelease(_requestBody);
    NSMutableData *requestBody = [[NSMutableData alloc] init];
    _requestBody = requestBody;
    
    
    NSString *bodyPrefixString = [[NSString alloc] initWithFormat:@"--%@\r\n", _requestBoundary];
    NSString *bodySuffixString = [[NSString alloc] initWithFormat:@"\r\n--%@--\r\n", _requestBoundary];
    NSData *bodyPrefixData = [bodyPrefixString newDataUsingUTF8StringEncoding];
    NSData *bodySuffixData = [bodySuffixString newDataUsingUTF8StringEncoding];
    TTRelease(bodyPrefixString);
    TTRelease(bodySuffixString);
    
    
    
    // Begin
    [requestBody appendData:bodyPrefixData];
    
    
    NSMutableDictionary *dataDictionary = [[NSMutableDictionary alloc] init];
    
    for ( id key in [parameters keyEnumerator] ) {
        id value = [parameters objectForKey:key];
        if (([value isKindOfClass:[UIImage class]]) || 
            ([value isKindOfClass:[NSData class]]))
        {
            
            [dataDictionary setObject:value forKey:key];
            
        } else {
            
            NSString *disposition = [[NSString alloc] initWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n", key, value];
            NSData *dispositionData = [disposition newDataUsingUTF8StringEncoding];
            [requestBody appendData:dispositionData];
            TTRelease(dispositionData);
            TTRelease(disposition);
            
            [requestBody appendData:bodyPrefixData];
        }
    }
    
    if ( [dataDictionary count] > 0 ) {
        for ( id key in dataDictionary ) {
            
            id data = [dataDictionary valueForKey:key];
            
            if ( [data isKindOfClass:[UIImage class]] ) {
                
                NSString *disposition = [[NSString alloc] initWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"file.png\"\r\n", key];
                NSData *dispositionData = [disposition newDataUsingUTF8StringEncoding];
                [requestBody appendData:dispositionData];
                TTRelease(dispositionData);
                TTRelease(disposition);
                
                NSString *type = [[NSString alloc] initWithString:@"Content-Type: image/png\r\nContent-Transfer-Encoding: binary\r\n\r\n"];
                NSData *typeData = [type newDataUsingUTF8StringEncoding];
                [requestBody appendData:typeData];
                TTRelease(typeData);
                TTRelease(type);
                
                NSData *imageData = UIImagePNGRepresentation((UIImage *)data);
                [requestBody appendData:imageData];
                
            } else if ( [data isKindOfClass:[NSData class]] ) {
                
                NSString *disposition = [[NSString alloc] initWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", key];
                NSData *dispositionData = [disposition newDataUsingUTF8StringEncoding];
                [requestBody appendData:dispositionData];
                TTRelease(dispositionData);
                TTRelease(disposition);
                
                NSString *type = [[NSString alloc] initWithString:@"Content-Type: content/unknown\r\nContent-Transfer-Encoding: binary\r\n\r\n"];
                NSData *typeData = [type newDataUsingUTF8StringEncoding];
                [requestBody appendData:typeData];
                TTRelease(typeData);
                TTRelease(type);
                
                [requestBody appendData:(NSData *)data];
                
            }
            
            [requestBody appendData:bodySuffixData];
        }
    }
    
    TTRelease(bodyPrefixData);
    TTRelease(bodySuffixData);
    
    TTRelease(dataDictionary);
    
}


+ (NSOperationQueue *)requestOperationQueue
{
    static NSOperationQueue *sharedRequestOperationQueue = nil;
    if ( sharedRequestOperationQueue == nil ) {
        sharedRequestOperationQueue = [[NSOperationQueue alloc] init];
        [sharedRequestOperationQueue setMaxConcurrentOperationCount:4];
    }
    return sharedRequestOperationQueue;
}

+ (NSThread *)requestThread
{
    static NSThread *sharedRequestThread = nil;
    if ( sharedRequestThread == nil ) {
        sharedRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(requestThreadBody:) object:nil];
        [sharedRequestThread start];
    }
    return sharedRequestThread;
}



#pragma mark - NSOperation

- (void)start
{
    if ( [self isCancelled] ) {
        [self willChangeValueForKey:@"isFinished"];
        _finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    
    if ( [self isExecuting] ) {
        return;
    }
    
    if ( [self isFinished] ) {
        return;
    }
    
    if ( [self isReady] ) {
        [self performSelector:@selector(main) 
                     onThread:[[self class] requestThread]
                   withObject:nil 
                waitUntilDone:YES 
                        modes:[NSArray arrayWithObject:_runLoopMode]];
    }
}

- (void)cancel
{
    if ( [self isCancelled] ) {
        return;
    }
    
    if ( [self isFinished] ) {
        return;
    }
    
    if ( [self isReady] ) {
    }
    
    if ( [self isExecuting] ) {
        
        [_connection cancel];
        TTRelease(_connection);
        
        TTRelease(_response);
        TTRelease(_responseData);
        
        if ( _destinationPath ) {
            [_responseFileHandle closeFile];
            TTRelease(_responseFileHandle);
            [[NSFileManager defaultManager] removeItemAtPath:_destinationPath error:NULL];
        }
        
        [self operationDidFailWithError:[NSError errorWithDescription:@"HTTPRequest cancelled"]];
        [self endUsingNetwork];
        
        [self willChangeValueForKey:@"isFinished"];
        [self willChangeValueForKey:@"isExecuting"];
        _executing = NO;
        _finished = YES;
        [self didChangeValueForKey:@"isExecuting"];
        [self didChangeValueForKey:@"isFinished"];
        
    }
    
    [super cancel];
}


- (void)main
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    if ( ![self isCancelled] ) {
        
        [self beginUsingNetwork];
        
        [self operationDidStart];
        
        
        _step = TTOperationStepExecuting;
        [self willChangeValueForKey:@"isExecuting"];
        [self willChangeValueForKey:@"isReady"];
        _ready = NO;
        _executing = YES;
        [self didChangeValueForKey:@"isReady"];
        [self didChangeValueForKey:@"isExecuting"];
        
        
        NSURL *URL = [[NSURL alloc] initWithString:_address];
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL 
                                                                    cachePolicy:_cachePolicy 
                                                                timeoutInterval:_timeoutInterval];
        TTRelease(URL);
        [self addHeadersForRequest:request];
        [self addBodyForRequest:request];
        [self addMethodForRequest:request];
        
        _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
        [_connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:_runLoopMode];
        [_connection start];
        
        TTRelease(request);
    }
    
    [pool release];
}


#pragma mark - NSURLConnectionDataDelegate

//- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response {}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    
    if ( _destinationPath ) {
        NSData *data = [[NSData alloc] init];
        [[NSFileManager defaultManager] createFileAtPath:_destinationPath contents:data attributes:nil];
        TTRelease(data);
        NSFileHandle *responseFileHandle = [NSFileHandle fileHandleForUpdatingAtPath:_destinationPath];
        TTRetain(responseFileHandle, _responseFileHandle);
    } else {
        _responseData = [[NSMutableData alloc] init];
    }
    
    
    TTRetain(response, _response);
    
    
    if ( _showAccurateProgress ) {
        _lastBytesRead = 0;
        _totalBytesRead = 0;
        _contentLength = [[self.responseHeaders objectForKey:@"Content-Length"] intValue];
        _progress = 0.0;
        
        [self operationUpdated];
    }
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    
    if ( _destinationPath ) {
        [_responseFileHandle seekToEndOfFile];
        [_responseFileHandle writeData:data];
        [_responseFileHandle synchronizeFile];
    } else {
        [(NSMutableData *)_responseData appendData:data];
    }
    
    
    if ( _showAccurateProgress ) {
        _lastBytesRead = [data length];
        _totalBytesRead += _lastBytesRead;
        if ( _contentLength < _totalBytesRead ) _contentLength = _totalBytesRead;
        if ( _contentLength > 0 ) _progress = ((CGFloat)_totalBytesRead) / ((CGFloat)_contentLength);
        
        [self operationUpdated];
    }
}

//- (NSInputStream *)connection:(NSURLConnection *)connection needNewBodyStream:(NSURLRequest *)request {}
//- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {}

//- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    
    if ( _destinationPath ) {
        [_responseFileHandle closeFile];
        TTRelease(_responseFileHandle);
    }
    
    [self operationWillFinish];
    [self operationDidFinish];
    
    _step = TTOperationStepFinished;
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
    _executing = NO;
    _finished = YES;
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
    
    [self endUsingNetwork];
}

#pragma mark - NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)err
{
    
    if ( _destinationPath ) {
        [_responseFileHandle closeFile];
        TTRelease(_responseFileHandle);
    }
    
    TTRelease(_response);
    TTRelease(_responseData);
    [[NSFileManager defaultManager] removeItemAtPath:_destinationPath error:NULL];
    
    
    [self operationDidFailWithError:[NSError errorWithDescription:@"Request failed"]];
    
    _step = TTOperationStepFinished;
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
    _executing = NO;
    _finished = YES;
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
    
    [self endUsingNetwork];
}

//- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection {}
//- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {}

@end
